package com.example.jwt.framework.config;

import com.example.jwt.framework.security.filter.JwtAuthenticationTokenFilter;
import com.example.jwt.framework.security.handle.AuthenticationEntryPointImpl;
import com.example.jwt.framework.security.handle.CustomExpiredSessionStrategyImpl;
import com.example.jwt.framework.security.handle.LogoutSuccessHandlerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.session.HttpSessionEventPublisher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Arrays;

import static org.springframework.security.config.Customizer.withDefaults;

/**
 * @version 1.0.0
 * @className: SecurityConfig
 * @description: SpringSecurity 5.7.x新用法配置
 * @author: LiJunYi
 * @create: 2022/7/26 8:43
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig
{
    /**
     * 自定义用户登录处理逻辑
     */
    private final UserDetailsService userDetailsService;

    /**
     * 注销成功后处理器
     */
    private final LogoutSuccessHandlerImpl logoutSuccessHandler;

    /**
     * 无权限处理器
     */
    private final AuthenticationEntryPointImpl unauthorizedHandler;

    /**
     * 并发登录控制处理器
     */
    private final CustomExpiredSessionStrategyImpl expiredSessionStrategy;

    /**
     * jwt身份验证令牌过滤器
     */
    private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    /**
     * 安全配置
     * 构造函数注入
     *
     * @param userDetailsService           用户详细信息服务
     * @param logoutSuccessHandler         退出处理器
     * @param unauthorizedHandler          无权限处理器
     * @param expiredSessionStrategy       并发登录控制处理器
     * @param jwtAuthenticationTokenFilter JWT登录过滤器
     */
    @Autowired
    public SecurityConfig(UserDetailsService userDetailsService, LogoutSuccessHandlerImpl logoutSuccessHandler,
                          AuthenticationEntryPointImpl unauthorizedHandler,
                          CustomExpiredSessionStrategyImpl expiredSessionStrategy,
                          JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter) {
        this.jwtAuthenticationTokenFilter = jwtAuthenticationTokenFilter;
        this.userDetailsService = userDetailsService;
        this.logoutSuccessHandler = logoutSuccessHandler;
        this.unauthorizedHandler = unauthorizedHandler;
        this.expiredSessionStrategy = expiredSessionStrategy;
    }


    /**
     * 获取AuthenticationManager（认证管理器），登录时认证使用
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        AuthenticationManager authenticationManager = configuration.getAuthenticationManager();
        return configuration.getAuthenticationManager();
    }

    /**
     * 配置加密方式
     *
     * @return {@link PasswordEncoder}
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    /**
     * 侦听器配置，使 Spring Security 更新有关会话生命周期事件的信息
     * 并发会话控制：https://docs.spring.io/spring-security/reference/servlet/authentication/session-management.html
     * @return {@link HttpSessionEventPublisher}
     */
    @Bean
    public HttpSessionEventPublisher httpSessionEventPublisher() {
        return new HttpSessionEventPublisher();
    }

    /**
     * 过滤器链
     *
     * @param http http
     * @return {@link SecurityFilterChain}
     * @throws Exception 异常
     */
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception
    {
        http.authorizeRequests(authorize ->
                authorize.mvcMatchers("/userLogin","/noPermission").permitAll()
                        .anyRequest().authenticated()
        )
                .csrf().disable()
                // 基于token，不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .headers().frameOptions().disable();
        // 退出过滤器
        http.logout(logout -> logout
                .logoutUrl("/logout")
                .deleteCookies("JSESSIONID")
                .logoutSuccessHandler(logoutSuccessHandler));
        // 添加JWT filter
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        // by default uses a Bean by the name of corsConfigurationSource
        http.cors(withDefaults());
        // 无权限处理器
        http.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).accessDeniedPage("/noPermission");
        // 并发会话控制
        http.sessionManagement(session ->  session
                .maximumSessions(1)
                .expiredSessionStrategy(expiredSessionStrategy));
        http.userDetailsService(userDetailsService);
        return http.build();
    }

    /**
     * 配置跨源访问(CORS)
     * 官方文档：https://docs.spring.io/spring-security/reference/servlet/integrations/cors.html
     * @return {@link CorsConfigurationSource}
     */
    @Bean
    CorsConfigurationSource corsConfigurationSource()
    {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("http://localhost:8080/","https://example.com"));
        configuration.setAllowedMethods(Arrays.asList("GET","POST"));
        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
}
